package com.netty.quartz.util;

import com.netty.common.enums.ExceptionEnum;
import com.netty.common.exception.BizException;
import com.netty.common.util.bean.SpringUtils;
import com.netty.quartz.entity.SysJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

/** 反射执行任务方法
 * @author LiuXiaoYu
 * @date 2021/10/16- 10:19
 */
@Slf4j
public class ReflectInvokeJobUtil {
    /**
     * 反射执行系统任务 即反射执行具体的方法
     * @param sysJob 任务信息对象
     */
    public static void invokeMethod(SysJob sysJob) {
        //获取要执行的方法
        //bean调用->类名.方法名(参数)
        //class调用->包名.类名.方法名(参数)
         String invokeTarget = sysJob.getInvokeTarget();
        //获取beanName
        String beanName=getBeanNameAndMethodName(invokeTarget).get(0);
        //获取methodName
        String methodName=getBeanNameAndMethodName(invokeTarget).get(1);
        //获取methodParams
        List<Object[]> methodParams=getMethodParams(invokeTarget);
         //需要判断是通过bean调用方法 还是通过class调用方法
        if (!isClassName(invokeTarget)) {
            Object bean = SpringUtils.getBean(beanName);
            try {
                invokeMethod(bean, methodName, methodParams);
            } catch (Exception e) {
                log.error("Job类名/方法名或者执行方法抛出异常不正确,找不到正确的执行任务方法,异常:{},异常信息:{}",e,e.getMessage());
                throw new BizException(ExceptionEnum.ERROR,"Job类名/方法名或者执行方法抛出异常不正确,找不到正确的执行任务方法");
            }
        } else {
            Object bean;
            try {
                bean = Class.forName(beanName).newInstance();
                invokeMethod(bean, methodName, methodParams);
            } catch (Exception e) {
               log.error("Job包名/方法名或者执行方法抛出异常不正确,找不到正确的执行任务方法,异常:{},异常信息:{}",e,e.getMessage());
               throw new BizException(ExceptionEnum.ERROR,"Job包名/方法名或者执行方法抛出异常不正确,找不到正确的执行任务方法");
            }
        }
    }

    /**
     * 判断是不是包名 通过是否有多个点间隔来判断
     * @param invokeTarget 字符串
     * @return true|false
     */
    private static boolean isClassName(String invokeTarget) {
        return StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    /**
     * 调用任务方法
     *
     * @param bean 目标对象
     * @param methodName 方法名称
     * @param methodParams 方法参数
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        //带参数的方法 反射强制执行
        if (Objects.nonNull(methodParams) && methodParams.size() > 0) {
            Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
            method.setAccessible(true);
            method.invoke(bean, getMethodParamsValue(methodParams));
        } else {
            //不带参数的方法 反射强制执行
            Method method = bean.getClass().getDeclaredMethod(methodName);
            method.setAccessible(true);
            method.invoke(bean);
        }
    }

    /**
     * 根据前端传递的执行字符串 获取beanName
     * @param invokeTarget 字符串
     * @return beanName
     */
    private static List<String> getBeanNameAndMethodName(String invokeTarget) {
        List<String> beanNameAndMethodName=new ArrayList<>();
        String beanPointMethod = StringUtils.substringBefore(invokeTarget, "(");
        String  beanName= StringUtils.substringBeforeLast(beanPointMethod, ".");
        String methodName=StringUtils.substringAfter(beanPointMethod,".");
        beanNameAndMethodName.add(beanName);
        beanNameAndMethodName.add(methodName);
        return beanNameAndMethodName;
    }

    /**
     * 获取method方法参数相关列表
     *
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget) {
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr))
        {
            return null;
        }
        String[] methodParams = methodStr.split(",");
        List<Object[]> classs = new LinkedList<>();
        for (String methodParam : methodParams) {
            String str = StringUtils.trimToEmpty(methodParam);
            // String字符串类型，包含'
            if (StringUtils.contains(str, "'")) {
                classs.add(new Object[]{StringUtils.replace(str, "'", ""), String.class});
            }
            // boolean布尔类型，等于true或者false
            else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
                classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
            }
            // long长整形，包含L
            else if (StringUtils.containsIgnoreCase(str, "L")) {
                classs.add(new Object[]{Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class});
            }
            // double浮点类型，包含D
            else if (StringUtils.containsIgnoreCase(str, "D")) {
                classs.add(new Object[]{Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class});
            }
            // 其他类型归类为整形
            else {
                classs.add(new Object[]{Integer.valueOf(str), Integer.class});
            }
        }
        return classs;
    }

    /**
     * 获取参数类型
     *
     * @param methodParams 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
    {
        Class<?>[] classs = new Class<?>[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams)
        {
            classs[index] = (Class<?>) os[1];
            index++;
        }
        return classs;
    }

    /**
     * 获取参数值
     *
     * @param methodParams 参数相关列表
     * @return 参数值列表
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams)
    {
        Object[] classs = new Object[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams)
        {
            classs[index] = os[0];
            index++;
        }
        return classs;
    }
}
